home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / sbin / update-python-modules < prev    next >
Text File  |  2009-05-13  |  20KB  |  494 lines

  1. #! /usr/bin/python
  2. #
  3. # copyright (c) 2006 Josselin Mouette <joss@debian.org>
  4. # Licensed under the GNU Lesser General Public License, version 2.1
  5. # See COPYING for details
  6.  
  7. # Everything prefixed by old_ is compatibility code with older versions
  8. # Modules used to lie in /usr/{lib,share}/python-support/$package
  9. # They now lie in /usr/{lib,share}/pyshared
  10.  
  11. import sys,os,shutil
  12. from optparse import OptionParser
  13. from subprocess import call
  14. from py_compile import compile, PyCompileError
  15. sys.path.append("/usr/share/python-support/private/")
  16. import pysupport
  17. from pysupport import py_supported,py_installed,py_oldversions
  18.  
  19. basepath='/usr/lib/pymodules'
  20. sourcepath='/usr/share/python-support'
  21. old_extensionpath='/usr/lib/python-support'
  22. shared_path='/usr/share/pyshared'
  23. shared_extensionpath='/usr/lib/pyshared'
  24.  
  25. parser = OptionParser(usage="usage: %prog [-v] [-c] package_directory [...]\n"+
  26.                             "       %prog [-v] [-c] package.dirs [...]\n"+
  27.                             "       %prog [-v] [-a|-f|-p]")
  28. parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
  29.                   help="verbose output", default=False)
  30. parser.add_option("-c", "--clean", action="store_true", dest="clean_mode",
  31.                   help="clean modules instead of compiling them",
  32.                   default=False)
  33. parser.add_option("-a", "--rebuild-all", action="store_true",
  34.                   dest="rebuild_all", default=False,
  35.                   help="rebuild all private modules for a new default python version")
  36. parser.add_option("-f", "--force-rebuild-all", action="store_true",
  37.                   dest="rebuild_everything", default=False,
  38.                   help="rebuild all modules, including public modules for all python versions")
  39. parser.add_option("-p", "--post-install", action="store_true", dest="post_install",
  40.                   help="run post-installation operations, common to many packages",
  41.                   default=False)
  42. parser.add_option("-b", "--bytecompile", action="store_true", dest="force_private",
  43.                   help="[deprecated] byte-compilation mode: only handle private modules",
  44.                   default=False)
  45. parser.add_option("-i", "--install", action="store_true", dest="force_public",
  46.                   help="[deprecated] installation mode: only handle public modules",
  47.                   default=False)
  48. (options, args) = parser.parse_args()
  49.  
  50. def debug(x):
  51.     if(options.verbose):
  52.         print x
  53.  
  54. def warning(x):
  55.     sys.stderr.write("WARNING: %s\n"%x)
  56.  
  57. def isect(l1,l2):
  58.     return [i for i in l1 if i in l2]
  59.  
  60. def concat(l1,l2):
  61.     return l1 + [i for i in l2 if i not in l1]
  62.  
  63.  
  64. # Abstract class implementing the methods related to public modules
  65. class _PublicList (list):
  66.     pyversions = py_supported
  67.     def install (self, versions):
  68.         versions = isect (self.pyversions, versions)
  69.         for filename in self:
  70.             version = None
  71.             rng = versions
  72.             try:
  73.                 if filename.startswith (shared_path+"/"):
  74.                     # New layout, module
  75.                     relname = filename[len(shared_path)+1:]
  76.                 elif filename.startswith (shared_extensionpath+"/python"):
  77.                     # New layout, extension
  78.                     [ version, relname ] = filename[len(shared_extensionpath)+1:].split("/", 1)
  79.                 elif filename.startswith (sourcepath+"/"):
  80.                     [ package, relname ] = filename[len(sourcepath)+1:].split("/",1)
  81.                 elif filename.startswith (old_extensionpath+"/"):
  82.                     [ package, version, relname ] = filename[len(old_extensionpath)+1:].split("/",2)
  83.                 else:
  84.                     raise ValueError
  85.             except ValueError:
  86.                 warning ("%s contains an invalid filename (%s)"%(self.name, filename))
  87.                 continue
  88.             if version:
  89.                 if version not in versions:
  90.                     continue
  91.                 rng = [version]
  92.             for pyversion in rng:
  93.                 destpath = os.path.join (basepath, pyversion, relname)
  94.                 try:
  95.                     os.makedirs(os.path.dirname(destpath))
  96.                 except OSError:
  97.                     pass
  98.                 if filename[-4:] not in ['.pyc', '.pyo']:
  99.                     debug("link "+destpath)
  100.                     # os.path.exists returns False for broken symbolic links
  101.                     if os.path.exists(destpath) or os.path.islink(destpath):
  102.                         if file!="__init__.py" or (os.path.exists(destpath) and os.path.getsize(destpath)):
  103.                             # The file is already here, probably from the previous version. 
  104.                             # No need to check for conflicts, dpkg catches them earlier now
  105.                             debug("overwrite "+destpath)
  106.                         else:
  107.                             debug("overwrite namespace "+destpath)
  108.                         os.remove(destpath)
  109.                     os.symlink(filename,destpath)
  110.  
  111.  
  112. # Abstract class implementing the methods related to private modules
  113. class _PrivateList (list):
  114.     pyversion = None
  115.     def bytecompile (self):
  116.         if self.pyversion:
  117.             debug("Byte-compilation of whole %s with python%s..."%(self.name,self.pyversion))
  118.             call(['/usr/bin/python'+self.pyversion, 
  119.                   os.path.join('/usr/lib','python'+self.pyversion,'py_compile.py')]
  120.                  + self)
  121.         else:
  122.             for filename in self:
  123.                 debug("compile "+filename+'c')
  124.                 try:
  125.                     # Note that compile doesn't raise PyCompileError by default
  126.                     compile(filename, doraise=True)
  127.                 except IOError, (errno, strerror):
  128.                     warning("I/O error while trying to byte-compile %s (%s): %s" % (filename, errno, strerror))
  129.                 except PyCompileError, inst:
  130.                     warning("compile error while trying to byte-compile %s: %s" % (filename, inst.msg))
  131.                 except:
  132.                     warning("unexpected error while trying to byte-compile %s: %s" % (filename, sys.exc_info()[0]))
  133.     def clean(self):
  134.         for filename in self:
  135.             for ext in ['c', 'o']:
  136.                 fullpath=filename+ext
  137.                 if os.path.exists(fullpath):
  138.                     debug("remove "+fullpath)
  139.                     os.remove(fullpath)
  140.  
  141.  
  142. # Abstract class for PrivateFileList and SharedFileList
  143. class _FileList(list):
  144.     def __init__ (self, path):
  145.         self.name = path
  146.         for line in file(path):
  147.             line = line.strip()
  148.             if (not line) or line.startswith('#'):
  149.                 continue
  150.             if line.startswith('/'):
  151.                 self.append(line)
  152.                 continue
  153.             line = [x.strip() for x in line.split('=',1)]
  154.             if len(line) != 2:
  155.                 warning("Parse error in %s"%path)
  156.                 continue
  157.             self.parse_option(*line)
  158.  
  159. # This class represents a file list as provided in the /usr/share/python-support/$package.public
  160. # Useful for public modules and extensions
  161. class SharedFileList(_FileList, _PublicList):
  162.     def parse_option (self, arg, value):
  163.         if arg=='pyversions':
  164.             self.pyversions = pysupport.version_list(value)
  165.         # Ignore unknown arguments for extensivity
  166.  
  167. # This class represents a file list as provided in the /usr/share/python-support/$package.private
  168. # Useful for private modules
  169. class PrivateFileList(_FileList, _PrivateList):
  170.     def parse_option (self, arg, value):
  171.         if arg=='pyversion':
  172.             self.pyversion = value
  173.  
  174. # This is a helper generator that goes through files of interest in a given directory
  175. def allfiles(path, onlypy=False):
  176.     for root, dirs, files in os.walk(path):
  177.         for f in files:
  178.             if (onlypy and not f.endswith(".py")) or f== ".version":
  179.                 continue
  180.             yield os.path.join(root,f)
  181.         if not onlypy:
  182.             for d in dirs:
  183.                 d = os.path.join(root, d)
  184.                 if os.path.islink(d):
  185.                     yield d
  186.  
  187. # This class emulates the file listing as provided by /usr/share/python-support/$package.public
  188. # with the deprecated layout /usr/{lib,share}/python-support/$package/
  189. class SharedDirList(_PublicList):
  190.     def __init__ (self, path):
  191.         self.name = path
  192.         # Add all files to the file listing
  193.         self.extend(allfiles(path))
  194.         verfile=os.path.join(path,'.version')
  195.         extdir=path.replace(sourcepath,old_extensionpath,1)
  196.         if os.path.isfile(verfile):
  197.             # If we have a .version, use it
  198.             self.pyversions = pysupport.version_list(file(verfile).readline())
  199.         elif os.path.isdir(extdir):
  200.             # Try to obtain the list of supported versions
  201.             # from the extensions in /usr/lib
  202.             self.pyversions = isect(py_supported,os.listdir(extdir))
  203.         else:
  204.             # Otherwise, support all versions
  205.             pass
  206.  
  207.         if os.path.isdir(extdir):
  208.             # Add the extensions to the file listing
  209.             for version in self.pyversions:
  210.                 self.extend(allfiles(os.path.join(extdir,version)))
  211.  
  212. # This class emulates the file listing as provided by /usr/share/python-support/$package.private
  213. # with the deprecated layout /usr/share/python-support/$package.dirs
  214. class PrivateDirList(_PrivateList):
  215.     def __init__ (self, path):
  216.         self.name = path
  217.         self.extend(allfiles(path, onlypy=True))
  218.         versionfile = os.path.join(path, ".pyversion")
  219.         if os.path.isfile(versionfile):
  220.             self.pyversion = file(versionfile).readline().strip()
  221.  
  222.  
  223. class CachedFileList(dict):
  224.     def __getitem__ (self, name):
  225.         if name in self and dict.__getitem__(self, name) == None:
  226.             if name.startswith("/"):
  227.                 # The case of old-style private directories
  228.                 self[name] = PrivateDirList (name)
  229.             else:
  230.                 path = os.path.join (sourcepath, name)
  231.                 if name.endswith(".public"):
  232.                     self[name] = SharedFileList (path)
  233.                 elif name.endswith(".private"):
  234.                     self[name] = PrivateFileList (path)
  235.                 elif os.path.isdir(path):
  236.                     self[name] = SharedDirList (path)
  237.                 else:
  238.                     raise "[Internal Error] I don't know what to do with this path: %s"%path
  239.         return dict.__getitem__(self, name)
  240.  
  241.  
  242. def bytecompile_all(py,path=None):
  243.     if not path:
  244.         path=os.path.join(basepath,py)
  245.     if not os.path.isdir(path):
  246.         return
  247.     debug("Byte-compilation of whole %s..."%path)
  248.     os.spawnl(os.P_WAIT, '/usr/bin/'+py, py,
  249.               os.path.join('/usr/lib/',py,'compileall.py'), '-q', path)
  250.  
  251. # A function to create the ".path" at the root of the installed directory
  252. # Returns the list of affected directories
  253. def create_dotpath(py):
  254.   path=os.path.join(basepath,py)
  255.   if not os.path.isdir(path):
  256.     return
  257.   pathfile=os.path.join(path,".path")
  258.   debug("Generation of %s..."%pathfile)
  259.   pathlist=[path]
  260.   ret=[]
  261.   for f in os.listdir(path):
  262.     f=os.path.join(path,f)
  263.     if f.endswith(".pth") and os.path.isfile(f):
  264.       for l in file(f):
  265.         l=l.rstrip('\n')
  266.         if l.startswith('import'):
  267.           # Do not ship lines starting with "import", they are executed! (complete WTF)
  268.           continue
  269.         pathlist.append(l)
  270.         l2=os.path.join(path,l)
  271.         pathlist.append(l2)
  272.         ret.append(l2)
  273.   fd=file(pathfile,"w")
  274.   fd.writelines([l+'\n' for l in pathlist])
  275.   fd.close()
  276.   return ret
  277.  
  278. def post_change_stuff(py):
  279.   # All the changes that need to be done after anything has changed
  280.   # in a /usr/lib/pymodules/pythonX.Y directory
  281.   # * Cleanup of all dangling symlinks that are left out after a package
  282.   #   is upgraded/removed.
  283.   # * The namespace packages are here because python doesn't consider a
  284.   #   directory to be able to contain packages if there is no __init__.py
  285.   #   file (yes, this is completely stupid).
  286.   # * The .path file must be created by concatenating all those .pth
  287.   #   files that extend sys.path (this also badly sucks).
  288.   # * Byte-compilation of all .py files that haven't already been
  289.   path=os.path.join(basepath,py)
  290.   if not os.path.isdir(path):
  291.     return
  292.   # First, remove any dangling symlinks.
  293.   # In the same loop, we find which directories may need a namespace package
  294.   dirhash={}
  295.   for dir, dirs, files in os.walk(path):
  296.     dirhash[dir]=False
  297.     files.sort() # We need the .py to appear before the .pyc
  298.     for f in files+dirs:
  299.       # We also examine dirs as some symlinks are dirs
  300.       abspath=os.path.join(dir,f)
  301.       islink=os.path.islink(abspath)
  302.       if islink:
  303.         if not os.path.exists(abspath):
  304.           # We refer to a file that was removed
  305.           debug("remove "+abspath)
  306.           os.remove(abspath)
  307.           continue
  308.         srcfile = os.readlink (abspath)
  309.         # Remove links left here after a change in the supported python versions for a package
  310.         removed = False
  311.         for package in public_packages:
  312.           if srcfile in public_packages[package]:
  313.             if py not in public_packages[package].pyversions:
  314.               debug("remove "+abspath)
  315.               os.remove(abspath)
  316.               removed = True
  317.             break
  318.         else:
  319.           warning("WARNING: %s is linked but does not belong to any package."%srcfile)
  320.         if removed:
  321.           # Do not go further, the file was removed
  322.           continue
  323.       if f[-4:] in ['.pyc', '.pyo']:
  324.         if not os.path.exists(abspath[:-1]):
  325.           debug("remove "+abspath)
  326.           os.remove(abspath)
  327.           continue
  328.       elif f[-3:] in ['.py', '.so']:
  329.         if islink or f!='__init__.py':
  330.           # List the directory as maybe needing a namespace packages
  331.           d=dir
  332.           while dirhash.has_key(d) and not dirhash[d]:
  333.             dirhash[d]=True
  334.             d=os.path.dirname(d)
  335.     # Remove the directory if it is empty after our crazy removals
  336.     try:
  337.       os.removedirs(dir)
  338.     except OSError:
  339.       pass
  340.   dirhash[path]=False
  341.   # Then, find which directories belong in a .pth file
  342.   # These directories don't need a namespace package, so we
  343.   # set them to False in dirhash
  344.   for p in create_dotpath (py):
  345.     dirhash[p] = False
  346.   # Finally, create/remove namespace packages
  347.   for dir in dirhash:
  348.     initfile=os.path.join(dir,"__init__.py")
  349.     noinitfile=os.path.join(dir,".noinit")
  350.     if dirhash[dir] and not os.path.exists(noinitfile):
  351.       if not os.path.exists(initfile):
  352.         debug("create namespace "+initfile)
  353.         file(initfile,"w").close()
  354.     else:
  355.       for e in ['','c','o']:
  356.         if os.path.exists(initfile+e):
  357.           debug('remove namespace '+initfile+e)
  358.           os.remove(initfile+e)
  359.       try:
  360.         os.removedirs(dir)
  361.       except OSError:
  362.         pass
  363.   bytecompile_all(py)
  364.  
  365.  
  366. # A helper function for older $package.dirs files
  367. def dirlist_file(f):
  368.     return [ l.rstrip('\n') for l in file(f) if len(l)>1 ]
  369.  
  370. # End of function definitions - Start of the script itself
  371.  
  372. # Read all modules listing
  373. public_packages = CachedFileList()
  374. private_packages = CachedFileList()
  375. dirlisting = os.listdir(sourcepath)
  376. for name in dirlisting:
  377.     path=os.path.join(sourcepath,name)
  378.     if name == "private":
  379.         continue
  380.     ext = name.split(".")[-1]
  381.     if os.path.isdir(path):
  382.         if ext in ["public", "private", "dirs"]:
  383.             # Presumably a bogus directory, see #528130
  384.             warning("%s is a directory"%name)
  385.         else:
  386.             public_packages[name] = None
  387.         continue
  388.     if not os.path.isfile(path):
  389.         # Ignore whatever is not a file, like dangling symlinks
  390.         continue
  391.     if ext == "public":
  392.         public_packages[name] = None
  393.     elif ext == "private":
  394.         private_packages[name] = None
  395.     elif ext == "dirs":
  396.         for dirname in dirlist_file (path):
  397.             private_packages[dirname] = None
  398.     # Just ignore all other files
  399.  
  400. # Parse arguments
  401. do_public=[]
  402. do_private=[]
  403. for arg in args:
  404.     if arg.startswith(sourcepath):
  405.         arg = arg[len(sourcepath):].lstrip("/")
  406.     if arg.endswith(".dirs") and arg in dirlisting:
  407.         for dirname in dirlist_file(os.path.join(sourcepath, arg)):
  408.             do_private.append(private_packages[dirname])
  409.     elif arg in public_packages:
  410.         do_public.append(public_packages[arg])
  411.     elif arg in private_packages:
  412.         do_private.append(private_packages[arg])
  413.     else:
  414.         if options.clean_mode:
  415.             warning("%s does not exist.\n         Some bytecompiled files may be left behind."%arg)
  416.         else:
  417.             parser.error("%s is not a recognized python-support module."%arg)
  418.  
  419. # Check consistency options (although these ones should not exist anymore)
  420. if do_private and options.force_public:
  421.     parser.error("Option -i cannot be used with a .private module file.")
  422. if do_public and options.force_private:
  423.     parser.error("Option -b cannot be used with a .public module file.")
  424.  
  425. if options.rebuild_everything:
  426.     options.rebuild_all = True
  427.     for pyver in py_supported:
  428.         dir = os.path.join(basepath,pyver)
  429.         if os.path.isdir(dir):
  430.             shutil.rmtree(dir)
  431.  
  432. # Check for changes in installed python versions
  433. need_postinstall = []
  434. for pyver in py_oldversions+py_supported:
  435.     dir = os.path.join(basepath,pyver)
  436.     # Check for ".path" because sometimes the directory already exists 
  437.     # while the python version isn't installed, because of some .so's.
  438.     if pyver not in py_installed and os.path.isdir(dir):
  439.         debug("Removing obsolete directory %s..."%(dir))
  440.         shutil.rmtree(dir)
  441.     if pyver in py_installed and not os.path.isfile(os.path.join(dir,".path")):
  442.         need_postinstall.append(pyver)
  443. if need_postinstall:
  444.     debug("Building all modules for %s..."%(" ".join(need_postinstall)))
  445.     for package in public_packages:
  446.         public_packages[package].install(need_postinstall)
  447.     for pyver in need_postinstall:
  448.         # Here we need to launch create_dotpath because otherwise we could
  449.         # end up without the .path file that is checked 6 lines earlier
  450.         create_dotpath(pyver)
  451.  
  452. if options.rebuild_all:
  453.     for package in private_packages:
  454.         private_packages[package].bytecompile()
  455.  
  456.  
  457. # Now for the processing of what was handed on the command line
  458. for package in do_private:
  459.     if not options.clean_mode:
  460.         package.bytecompile()
  461.     else:
  462.         package.clean()
  463.  
  464. need_dotpath = False
  465. for package in do_public:
  466.     need_postinstall = concat (need_postinstall, isect(package.pyversions,py_installed))
  467.     if options.clean_mode:
  468.         continue
  469.     package.install(py_installed)
  470.     for f in package:
  471.         if f.endswith(".pth"):
  472.             need_dotpath = True
  473.  
  474. # Only do the funny and time-consuming things when the -p option is
  475. # given, e.g when python-support is triggered.
  476. if need_postinstall and 'DPKG_RUNNING_VERSION' in os.environ and not options.post_install:
  477.     ret = os.spawnlp(os.P_WAIT, 'dpkg-trigger', 'dpkg-trigger', '--no-await', 'pysupport')
  478.     if ret:
  479.         sys.stderr.write("ERROR: dpkg-trigger failed\n")
  480.         sys.exit(1)
  481.     if need_dotpath:
  482.         for py in need_postinstall:
  483.             create_dotpath (py)
  484.     need_postinstall = []
  485.  
  486. if options.post_install:
  487.     # The trigger has been activated; do it for all installed versions
  488.     need_postinstall = py_installed
  489. if need_postinstall:
  490.     need_dotpath = False
  491.     for py in need_postinstall:
  492.         post_change_stuff(py)
  493.  
  494.